package com.test.drawing;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView.Renderer;
import android.opengl.GLU;

import com.test.positioning.Projector;

/**
 * OpenGLRenderer.<br>
 * Class which prepares the field to draw a specified group of Meshes.<br>
 */
public class OpenGLRenderer implements Renderer {

	private static final int GROUPS_SIZE = 2;

	private Group[] groups = new Group[GROUPS_SIZE];
	
	private float[] globalAmbientColor = {1f, 1f, 1f, 1f};
	
	// Lightning Position (Last Element, 0 = Direction Light, 1 = Point Light)
	// Light comes from vector (0, 0, 1)
	private float[] lightDirection = { 0f, 0f, 1f, 0f };
	
	// Lightning Types
	private float[] ambientLightColor = {1f, 1f, 1f, 0f};
	private float[] diffuseLightColor = { 0.5f, 0.5f, 0.5f, 1f };
	private float[] specularLightColor = { 0f, 0f, 0f, 0f};
	
	/**
	 * Class Constructor.
	 */
	public OpenGLRenderer(float width, float height) {
		
		resetGroups();
	}

	/**
	 * Resets the Groups' List
	 */
	public void resetGroups() {
		for (int i = 0; i < groups.length; i++)
			groups[i] = new Group();
	}

	/**
	 * Gets the Meshes' Group of the given Position
	 * 
	 * @param position
	 *            Position of a Group
	 * @return null - invalid position<br>
	 *         Group - Group of the given Position
	 */
	public Group getGroup(int position) {
		return position >= groups.length ? null : groups[position];
	}

	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {

		// Set the background color to black ( rgba ).
		gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
		// Enable Smooth Shading, default not really needed.
		gl.glShadeModel(GL10.GL_SMOOTH);
		// Depth buffer setup.
		gl.glClearDepthf(1.0f);
		// Enables depth testing.
		gl.glEnable(GL10.GL_DEPTH_TEST);
		// The type of depth testing to do.
		gl.glDepthFunc(GL10.GL_LEQUAL);
		// Really nice perspective calculations.
		gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
	}

	@Override
	public void onDrawFrame(GL10 gl) {
		
		gl.glMatrixMode(GL10.GL_MODELVIEW);

		// "Vertical Sync" Initiation
		gl.glFlush();

		// Clears the screen and depth buffer.
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

		// Enable Blending Transparency
		gl.glEnable(GL10.GL_BLEND);
		
		// Anti Aliasing
		gl.glEnable(GL10.GL_POINT_SMOOTH);
		gl.glEnable(GL10.GL_LINE_SMOOTH);
		
		// Specifing Blending Function
		gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);

		// Replace the current matrix with the identity matrix
		gl.glLoadIdentity();
		
		// Gets the Current ModelView
		Projector.getInstance().getCurrentModelView(gl);
		
		// Update PositionChangers of the Projector
		Projector.getInstance().updatePositionChangersCoordinates();
		
		// Position the Camera (Under Tests)
		OpenGLCamera.positionCamera(gl);
		
		// Gets the Current ModelView
		Projector.getInstance().getCurrentModelView(gl);

		// Activate Lightning
		activeLightning(gl);
		
		enableDrawingState(gl);

		// Draw our scene.
		for (int i = 0; i < groups.length; i++)
			groups[i].draw(gl);
		
		disableDrawingState(gl);
		
		// Deactivate Lightning
		disableLightning(gl);
		
		// Disable Blending
		gl.glDisable(GL10.GL_BLEND);

		// "Vertical Sync" Finish
		gl.glFinish();
	}
	
	private void enableBlending(GL10 gl) {
		
	}
	
	private void disableBlending(GL10 gl) {
		
	}
	
	private void enableDrawingState(GL10 gl) {
		
		// Enable Texture
		gl.glEnable(GL10.GL_TEXTURE_2D);
		// Enable Texture Coord Array
		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		// Enable Normals Array
		gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
		// Enabled the vertices buffer for writing and to be used during
		// rendering.
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		
		// Counter-clockwise winding.
		gl.glFrontFace(GL10.GL_CCW);

		// Enable face culling.
		gl.glEnable(GL10.GL_CULL_FACE);

		// What faces to remove with the face culling.
		gl.glCullFace(GL10.GL_BACK);
		
		
	}
	
	private void disableDrawingState(GL10 gl) {
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
		// Disable face culling.
		gl.glDisable(GL10.GL_CULL_FACE);
	}

	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height) {
		// Sets the current view port to the new size.
		gl.glViewport(0, 0, width, height);
		Projector.getInstance().setCurrentView(0, 0, width, height);
		// Select the projection matrix
		gl.glMatrixMode(GL10.GL_PROJECTION);
		// Reset the projection matrix
		gl.glLoadIdentity();
		// Calculate the aspect ratio of the window
		GLU.gluPerspective(gl, 45.0f, (float) width / (float) height, 0.1f,
				1000.0f);
		Projector.getInstance().getCurrentProjection(gl);
		// Select the modelview matrix
		gl.glMatrixMode(GL10.GL_MODELVIEW);
		// Reset the modelview matrix
		gl.glLoadIdentity();
	}

	/*
	 * Activate the Lightning Souce
	 */
	private void activeLightning(GL10 gl) {

		// Enable Lightning
		gl.glEnable(GL10.GL_LIGHTING);
		gl.glEnable(GL10.GL_LIGHT0);
		
		gl.glLightModelfv(GL10.GL_LIGHT_MODEL_AMBIENT, globalAmbientColor, 0);

		// Ambient Light
		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, ambientLightColor, 0);
		// Diffuse Light
		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, diffuseLightColor, 0);
		// Specular Light
		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, specularLightColor, 0);
		// Lightning Position
		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightDirection , 0);
	}

	/*
	 * Disable Lightning Source
	 */
	private void disableLightning(GL10 gl) {
		gl.glDisable(GL10.GL_LIGHTING);
		gl.glDisable(GL10.GL_LIGHT0);
	}

	/**
	 * Adds a Drawable to the Group in the Given Position
	 * 
	 * @param drawable
	 *            Drawable to be Added
	 * @param groupPosition
	 *            Position of a Group
	 * @return True - Added<br>
	 *         False - Invalid Group Position or Adding Error
	 */
	public boolean addDrawable(Drawable drawable, int groupPosition) {
		if (groupPosition < groups.length)
			return groups[groupPosition].add(drawable);

		return false;
	}

	/**
	 * Adds a Drawable to the Group in the First Position
	 * 
	 * @param drawable
	 *            Drawable to be Added
	 * @return True - Added<br>
	 *         False - Adding Error
	 */
	public boolean addDrawable(Drawable drawable) {
		return addDrawable(drawable, 0);
	}

	/**
	 * Gets the Mesh's Groups Size of this Renderer
	 * 
	 * @return int - Groups Size
	 */
	public int getGroupsSize() {
		return GROUPS_SIZE;
	}
}
